package DispatchSys;

import Shared.MsgType;
import Shared.RadioMessage;
import Shared.Utils;

/**
 * The AmbulanceManager module, see Architecture report for specifications.
 */
public class AmbulanceManager implements Runnable {

	private RadioSoftware radio;
	private Database db;

	private static AmbulanceManager singleton = new AmbulanceManager();

	/**
	 * @return the unique AmbulanceManager instance.
	 */
	public static AmbulanceManager getInstance() {
		return singleton;
	}

	private AmbulanceManager() {
		this.radio = RadioSoftware.getInstance();
		this.db = Database.getInstance();
		// this.ic = new InterventionController(db);
	}

	/**
	 * Record a new ambulance into the system, and so into the database. This
	 * one is now eligible to be assigned directly. The init state is by default
	 * logged in. (cf AmbulanceInfo)
	 * 
	 * @param amb
	 *            != null, correctly initialized with its init location
	 */
	public void recordAmbulance(AmbulanceInfo amb) {
		this.db.addAmb(amb);
	}

	/**
	 * Suppress permanently a given ambulance from the system.
	 * 
	 * @param amb
	 */
	public void deleteAmbulance(AmbulanceInfo amb) {
		this.db.remAmb(amb);
	}

	/**
	 * Main loop to monitor all radio alert/events from the ambulances +
	 * interventionController and perform all updates of information.
	 */
	public void monitorRadioEvents() {
		RadioMessage newMessage;

		try {
			while (true) {
				newMessage = this.radio.listen();

				if (newMessage.getType() == MsgType.REGISTERING) {
					recordAmbulance(new AmbulanceInfo(newMessage.getAmbID(), newMessage.getAmbType(), null));
					continue;
				}

				AmbulanceInfo ambulance = this.db.getAmbulance(newMessage.getAmbID());

				synchronized (ambulance) {

					switch (newMessage.getType()) {

					case LOGGING_IN:
						ambulance.logIn();
						break;

					case SORRY_I_AM_BUSY:
						ambulance.setBusy();
						break;

					case YES_FREE:
						ambulance.confirmFree();
						break;

					case ACKNOWLEDGE_MISSION:
						ambulance.acceptMission();
						break;

					case MISSION_FAIL:
						ambulance.setMissionFail();
						// ambulance.logOut();
						break;

					case ON_MY_WAY:
						ambulance.acceptMission();
						break;

					case MISSION_SUCCESSFUL:
						Utils.disp("Amb ID " + newMessage.getAmbID() + " : Mission finished...");
						ambulance.setMissionFinished();
						break;

					case LOGGING_OUT:
						ambulance.logOut();
						break;

					case POSITION:
						ambulance.setPosition(newMessage.getPosition());
						break;

					case DEAD:
						ambulance.unregister();
						this.db.remAmb(newMessage.getAmbID());
						break;

					case IDLE:
						break; // nothing to do, ignored

					case FREE:
						ambulance.setFree();
						break;
					}

					ambulance.notifyAll();
				}
			}

		} catch (InterruptedException e) {
			// return
		}
	}

	/**
	 * Assign the ambulance in a to the Emergency in a. Makes sure that the real
	 * ambulance is free and send it the position corresponding to the
	 * emergency.
	 * 
	 * @param a
	 *            the Assignment
	 * @param waiting_time
	 *            the maximum time the ambulance has to confirm that it is free.
	 * @return true if the Assignment was correctly assigned. otherwise false
	 */
	public boolean addAssignment(Assignment a, long waiting_time) {
		try {
			Utils.disp("AmbMgr>>addAssignment");
			AmbulanceInfo amb = a.getAm();
			a.getAm().assign(a.getEr());
			this.radio.send(MsgType.AREYOUFREE, amb.getID());
			Utils.disp("AmbMgr>>Areyoufree sent");

			long begin_time = System.currentTimeMillis();
			long sleep;

			synchronized (amb) {
				while (!amb.isFreeConfirmed() && !amb.isBusy() && amb.isLogged()) {
					sleep = waiting_time - (System.currentTimeMillis() - begin_time);
					if (sleep <= 0)
						break;
					Utils.disp("AmbMgr>> before wait");
					amb.wait(sleep);
					Utils.disp("AmbMgr>> after wait");
				}
			}

			if (!amb.isFreeConfirmed()) {
				return false;
			}

			amb.clearConfirm();
			this.radio.send(a.getEr().getLocation(), amb.getID());
			this.db.addAssignment(a);
			// Wait reception
			while (!amb.isOnMission()) {
				synchronized (amb) {
					amb.wait();
				}
			}
			amb.startAssignment();

			return true;
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
			return false;
		}

	}

	/**
	 * Only removes an assignment from Database. Canceling an assignment in real
	 * time is not implemented yet.
	 * 
	 * @param a the assignment to remove
	 */
	public void deleteAssignment(Assignment a) {
		this.db.remAssignment(a);

		// TODO : Check where used
		// ensure coherence db <-> mdt.
		// Thoralf >> If we delete assignment because of a timeout, we should
		// send a message to the ambulance to drop the mission if they find out
		// about it later
	}

	@Override
	
	/**
	 * Starts monitorRadioEvents().
	 */
	public void run() {
		monitorRadioEvents();
	}

}
